package risoe.syslab.fhnew.hw;

/*
 * Copyright (c) 2012-2014, Technical University of Denmark (DTU)
 * All rights reserved.
 * 
 * The Flexhouse 2.0 platform ("this software") is licensed under the
 * BSD 3-clause license.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   - Neither the name of DTU nor the names of its contributors may be used to
 *     endorse or promote products derived from this software without specific
 *     prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE TECHNICAL UNIVERSITY OF DENMARK BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import java.net.InetAddress;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.MissingResourceException;

import risoe.syslab.comm.shared.RMITransportDetails;
import risoe.syslab.comm.typebased.rmi.MeteoStationRMIClient;
import risoe.syslab.fhnew.common.SystemConstants;
import risoe.syslab.fhnew.config.ActuatorConfig;
import risoe.syslab.fhnew.config.MetStationInterfaceConfig;
import risoe.syslab.fhnew.config.SensorConfig;
import risoe.syslab.model.CompositeMeasurement;
import risoe.syslab.model.Quality;
import risoe.syslab.model.Source;
import risoe.syslab.model.Validity;

public class MetStationInterface extends HardwareInterface implements Runnable{
	// Constants
	public static final String ATTR_CHANNEL="channel";
	private static final long POLLCYCLE=1000; // poll every 1s
	private static final String MET_INTERFACE_NAME = "typebased_Meteo";

	private RMITransportDetails mettd;
	private MeteoStationRMIClient metcli;
	private boolean mIsConnected=false;

	private InetAddress rmiHost;
	private String rmiUnit;

	CompositeMeasurement insol1, insol2, insol3;
	CompositeMeasurement temp;
	CompositeMeasurement wdir;
	CompositeMeasurement wspd;
	public static final String CHANNEL_INSOL1="insol1";
	public static final String CHANNEL_INSOL2="insol2";
	public static final String CHANNEL_INSOL3="insol3";
	public static final String CHANNEL_TEMP="temp";
	public static final String CHANNEL_WDIR="wdir";
	public static final String CHANNEL_WSPD="wspd";


	String[] CHANNELS={CHANNEL_INSOL1, CHANNEL_INSOL2, CHANNEL_INSOL2 ,CHANNEL_TEMP,CHANNEL_WDIR,CHANNEL_WSPD};
	private HashMap<String,String> channelMapping;
	private Thread mThread;

	public MetStationInterface(MetStationInterfaceConfig hic) {
		super(hic);
		insol1=new CompositeMeasurement(Double.NaN,Quality.FAILURE,Validity.INVALID,Source.UNKNOWN);
		insol2=new CompositeMeasurement(Double.NaN,Quality.FAILURE,Validity.INVALID,Source.UNKNOWN);
		insol3=new CompositeMeasurement(Double.NaN,Quality.FAILURE,Validity.INVALID,Source.UNKNOWN);
		temp=new CompositeMeasurement(Double.NaN,Quality.FAILURE,Validity.INVALID,Source.UNKNOWN);
		wdir=new CompositeMeasurement(Double.NaN,Quality.FAILURE,Validity.INVALID,Source.UNKNOWN);
		wspd=new CompositeMeasurement(Double.NaN,Quality.FAILURE,Validity.INVALID,Source.UNKNOWN);
		rmiHost=hic.getHostIPAddress();
		rmiUnit=hic.getUnit();
	}

	protected void startInterfaceImpl(ArrayList<SensorConfig> sensors, ArrayList<ActuatorConfig> actuators) {
		mapChannelsToSensors(sensors,CHANNELS);
		updateDataTable();

		getLogger().logEvent(getName(), "Init", "Setting up meteorology station interface");

		System.out.println("MetStation Interface Started");
		
		mThread = new Thread(this);
		mThread.start();
	}

	@Override
	public void run() {
		long t0,t1,td;
		updateDataTable();
		mIsConnected=false;
		getLogger().logEvent(getName(), "Init", "Starting meteorology station poll thread");
		Thread.currentThread().setName( "MetStationInterface '"+this.getName()+"'" );
		mettd=new RMITransportDetails(rmiHost.getHostAddress(),SystemConstants.DEFAULT_RMIPORT,MET_INTERFACE_NAME,rmiUnit);
		metcli=new MeteoStationRMIClient();
		t0=System.currentTimeMillis();
		t0-=(t0%POLLCYCLE); //round down to next multiple of pollcycle as start time. This should never be negative.
		td=t0+POLLCYCLE; //time of next poll
		t1=t0;
		while (!Thread.interrupted()) {
			t1=System.currentTimeMillis();
			while (td>t1) {
				try {
					Thread.sleep((td>t1)?td-t1:0);
				}
				catch (InterruptedException e) {} //ignore
				t1=System.currentTimeMillis();
			}

			if (!mIsConnected) {
				try {
					metcli.setTransportDetails(mettd);
					mIsConnected=true;
				}
				catch (MissingResourceException e) {
					mIsConnected=false;
				}
			}
			if (mIsConnected) {
				try {
					insol1=metcli.getInsolation(0);
					insol2=metcli.getInsolation(1);
					insol3=metcli.getInsolation(2);
					temp=metcli.getAirTemperature(0);
					wdir=metcli.getWindDirection(0);
					wspd=metcli.getWindSpeed(0);
					updateDataTable();
				}
				catch (RemoteException e) {
					mIsConnected=false;
					getLogger().logEvent(getName(), e.getClass().toString(), "during RMI call - "+e.getMessage());
				}
				catch (MissingResourceException e) {
					mIsConnected=false;
					getLogger().logEvent(getName(), e.getClass().toString(), "during RMI call - "+e.getMessage());
				}
				catch (Exception e) {
					//We can have all kinds of crap happening on the remote side. NPE and ArrayIndexOOB
					//can easily be thrown in all kinds of places if the order/indexing of instruments
					//changes at runtime.
					mIsConnected=false;
					getLogger().logEvent(getName(), e.getClass().toString(), "during RMI call - "+e.getMessage());
				}
			}
			
			// Mark data as old
			if (!mIsConnected){
				insol1=new CompositeMeasurement(insol1.value, Quality.OLDDATA, Validity.QUESTIONABLE, Source.PROCESS);
				insol2=new CompositeMeasurement(insol2.value, Quality.OLDDATA, Validity.QUESTIONABLE, Source.PROCESS);
				insol3=new CompositeMeasurement(insol3.value, Quality.OLDDATA, Validity.QUESTIONABLE, Source.PROCESS);
				temp=new CompositeMeasurement(temp.value, Quality.OLDDATA, Validity.QUESTIONABLE, Source.PROCESS);
				wdir=new CompositeMeasurement(wdir.value, Quality.OLDDATA, Validity.QUESTIONABLE, Source.PROCESS);
				wspd=new CompositeMeasurement(wspd.value, Quality.OLDDATA, Validity.QUESTIONABLE, Source.PROCESS);
			}

			td+=POLLCYCLE;
		}
	}

	private void updateDataTable() {
		mData.clear();
		if (channelMapping.get(CHANNEL_INSOL1)!=null)
			mData.put(channelMapping.get(CHANNEL_INSOL1),insol1);
		if (channelMapping.get(CHANNEL_INSOL2)!=null)
			mData.put(channelMapping.get(CHANNEL_INSOL2),insol2);
		if (channelMapping.get(CHANNEL_INSOL3)!=null)
			mData.put(channelMapping.get(CHANNEL_INSOL3),insol3);
		if (channelMapping.get(CHANNEL_TEMP)!=null)
			mData.put(channelMapping.get(CHANNEL_TEMP),temp);
		if (channelMapping.get(CHANNEL_WDIR)!=null)
			mData.put(channelMapping.get(CHANNEL_WDIR),wdir);
		if (channelMapping.get(CHANNEL_WSPD)!=null)
			mData.put(channelMapping.get(CHANNEL_WSPD),wspd);
		
		// Notify ProcessImage
		notifyProcessImage();
	}

	protected void stopInterfaceImpl() {
		insol1=new CompositeMeasurement(Double.NaN,Quality.FAILURE,Validity.INVALID,Source.UNKNOWN);
		insol2=new CompositeMeasurement(Double.NaN,Quality.FAILURE,Validity.INVALID,Source.UNKNOWN);
		insol3=new CompositeMeasurement(Double.NaN,Quality.FAILURE,Validity.INVALID,Source.UNKNOWN);
		temp=new CompositeMeasurement(Double.NaN,Quality.FAILURE,Validity.INVALID,Source.UNKNOWN);
		wdir=new CompositeMeasurement(Double.NaN,Quality.FAILURE,Validity.INVALID,Source.UNKNOWN);
		wspd=new CompositeMeasurement(Double.NaN,Quality.FAILURE,Validity.INVALID,Source.UNKNOWN);
		updateDataTable();
		channelMapping=null;
	}

	@Override
	public boolean isConnected() {
		return mIsConnected;
	}

	@Override
	public void applyActuation(Actuator actuator) {
		// This interface contains no actuators
	}

	private void mapChannelsToSensors(ArrayList<SensorConfig> sensors, String[] channels) {
		//Map channels to sensors
		channelMapping=new HashMap<String,String>();
		for (String chan:channels) {
			for (SensorConfig sc:sensors) {
				HashMap<String,String> attrs=sc.getInterfaceAttributes();
				String ch=attrs.get(ATTR_CHANNEL);
				if (ch!=null) {
					if (ch.equals(chan)) {
						channelMapping.put(chan, sc.getName());
					}
				}
				else {
					getLogger().logEvent(getName(), "Error", sc.getName()+"': Interface does not have a '"
							+ATTR_CHANNEL+"' attribute. Sensor cannot be mapped.");
				}
			}
		}
	}
}
